/*  
 * Copyright (c) 2010, James Daniello
 * This project is licensed under the 'New BSD' license.
 * The full text of the license can be found in the root
 * directory under the filename "LICENSE"
 */
package src.engine;

import java.awt.image.BufferedImage;
import java.awt.Graphics2D;
import java.awt.Color;
import java.awt.RenderingHints;
import java.util.Vector;
import java.util.HashMap;
import java.util.Scanner;
import java.io.File;
import javax.imageio.ImageIO;
import src.gui.PICAIUClient;
import src.gui.PICAIUClientGUI;
import java.awt.event.MouseListener;
import java.awt.event.MouseEvent;
import java.awt.event.KeyListener;
import java.awt.event.KeyEvent;
import java.awt.event.InputEvent;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JButton;
import javax.swing.ImageIcon;
import java.awt.Dimension;
import java.awt.BorderLayout;
import java.awt.Insets;
import java.awt.Font;
import javax.swing.JFormattedTextField;
import java.text.NumberFormat;
import javax.swing.text.NumberFormatter;
import java.text.ParseException;
import java.awt.Composite;
import java.awt.AlphaComposite;
import java.awt.BasicStroke;
import java.awt.Rectangle;
import java.awt.Shape;
import java.awt.geom.Ellipse2D;
import java.awt.event.ActionListener;
import java.awt.event.ActionEvent;
import java.awt.event.MouseMotionListener;
import java.awt.geom.AffineTransform;
import java.awt.geom.Area;
import java.util.Iterator;
import java.util.concurrent.ConcurrentLinkedQueue;


import src.objects.*;

/**
 * This class is both the drawing surface for the PICAIU Game Engine as well as the system for
 * storing and operating on game objects
 */
public class PICAIUGameEngine extends Thread implements MouseMotionListener {

  //used for version control
  private static final float VERSION = 0.23f;
  private static final boolean DRAWBOUNDS = false;
  private boolean imOnRedTeam = true;
  private int maxSecsToPlace = 10;
  private java.awt.Container frameParent;
  private JPanel frame;
  private JPanel bottom;
  private int canvasX, canvasY, canvasWidth, canvasHeight;
  private int mapWidth = 2000, mapHeight = 1500;
  private int screenOffX = 0, screenOffY = 0;
  private BufferedImage dbImage = null;
  private Graphics2D dbg;
  private int dimX, dimY;
  private boolean running = true;
  private static PICAIUJava2DImageOptimizer imageOptimizer = new PICAIUJava2DImageOptimizer();
  private PICAIUClientGUI gui;
  //HUD
  private int gap = 24;
  //target
  private long selectedId = -1;
  private long selectedIdLast = 0;
  private PICAIUGameUnit target = null;
  private int targetXLast = -20, targetYLast = -20;
  private float inAlpha = 0f, outAlpha = .25f;
  //viewer (selected troop)
  private int viewerX = 690, viewerY = 369;
  private int viewerWidth = 100, viewerHeight = 100;
  private int viewerOffsetX = 20, viewerOffsetY = 20;
  private PICAIUAnimation viewerAnim;
  //flags
  private int blueFlagX = 400 - 48 - gap, blueFlagY = 20;
  private int redFlagX = 400, redFlagY = 20;
  //money and enemy
  //shroud
  private Area shroud, shroudMini;
  //buttons
  private KeyListener keyListener;
  private boolean upPressed = false;
  private boolean downPressed = false;
  private boolean leftPressed = false;
  private boolean rightPressed = false;
  private boolean lBrackPressed = false;
  private boolean rBrackPressed = false;
  private int cycleTime = 0;
  private int cycleTimeReset = 75;
  //data
  private String me = "James";
  private long money = 100000;
  private String enemy = "TheInternetGuy01";
  private long enemyMoney = 10000;
  Font userMoney = new Font("Times New Roman", Font.BOLD, 16);
  Font userData = new Font("Times New Roman", Font.BOLD, 19);
  Font bounty = new Font("Times New Roman", Font.BOLD, 9);
  /*getInput queue. Input events will be added to the queue then the list of events will
   * be processed
   */
  PICAIUInputQueue inputQueue = new PICAIUInputQueue();
  //mouse locations
  private int mx, my;
  /*list of all game objects
   * gameObjects needs to house all objects which need to be drawn. Some of the other
   * lists below will be used for collision detection, etc.
   */
  private Vector<PICAIUGameObject> gameObjects = new Vector<PICAIUGameObject>();
  private HashMap<Long, PICAIUGameObject> gameObjectRefs = new HashMap<Long, PICAIUGameObject>();
  //list of all those objects which can collide with each other (excluding bullets)
  private Vector<PICAIUCollidable> collidables = new Vector<PICAIUCollidable>();
  //Red team units
  private Vector<PICAIUGameUnit> redUnits = new Vector<PICAIUGameUnit>();
  //Blue team units
  private Vector<PICAIUGameUnit> blueUnits = new Vector<PICAIUGameUnit>();
  //units the player can place
  private Vector<PICAIUGameUnit> toPlaceUnits = new Vector<PICAIUGameUnit>();
  private int awaitingNumber = -1;
  private float secsToPlace = maxSecsToPlace;
  private int placingIndex = 0;
  private boolean canPlace = false;
  //blue starting points
  private Vector<PICAIUVector> blueBases = new Vector<PICAIUVector>();
  //red starting pointsgame
  private Vector<PICAIUVector> redBases = new Vector<PICAIUVector>();
  //all bases
  private Vector<PICAIUVector> bases = new Vector<PICAIUVector>();
  private int baseVision = 600;
  private boolean baseAdd = true;
  private float baseColor = 0;
  //bounties
  private Vector<PICAIUBounty> bounties = new Vector<PICAIUBounty>();
  private boolean bountyMenu = false;
  private PICAIUBountyMenu[] bountyMenus = new PICAIUBountyMenu[]{new PICAIUBountyMenu(0),
    new PICAIUBountyMenu(1),
    new PICAIUBountyMenu(2)};
  private int bountyX, bountyY;
  private int bountyMenuSpacing = 18;
  private long attachedUnit = -1;
  private int bountyBoxes = 2;
  private String bountyAmountText = "0";
  private boolean bountyStart = true;
  //images for games. One copy of each saves performance but can be shroudMini hassle if images
  //need to be altered
  private HashMap<String, BufferedImage> gameImages = new HashMap<String, BufferedImage>();
  //imaging
  private int[][] grassMap;
  private HSBAdjustFilter satFilter = new HSBAdjustFilter(0, 0, 0);
  //game state
  private int gameState = 0;
  private final static int AWAITINGDATA = 0;
  private final static int PLACINGTROOPS = 1;
  private final static int AWAITINGENEMIES = 2;
  private final static int DRAWONCE = 3;
  private final static int FINISHEDPLACING = 4;
  private final static int RUNNING = 5;
  private int redCount = 0, blueCount = 0;
  private int retrieved = 0;

  /**
   * Create shroudMini new PICAIUGameEngine
   * @param frameIn the Container with which to draw the game onto
   * @param dimXIn the width dimension of the drawing surface
   * @param dimYIn the height dimension of the drawing surface
   */
  public PICAIUGameEngine(java.awt.Container frameIn, int dimXIn, int dimYIn, PICAIUClientGUI guiIn) {
    frameParent = frameIn;

    initGui();
    frameParent.addMouseMotionListener(this);

    dimX = dimXIn;
    dimY = dimYIn;
    canvasX = 0;
    canvasY = 0;
    canvasWidth = dimX;
    canvasHeight = dimY;


    gui = guiIn;
    enemy=gui.getEnemyName();
    me=gui.getUserName();

  }

  public void initGui() {

    frame = new JPanel();

    //bottom panel for setting bounties
    bottom = new JPanel();
    bottom.setBackground(Color.gray);
    bottom.setSize(new Dimension(dimX, 36));
    bottom.setMinimumSize(new Dimension(dimX, 36));
    bottom.setPreferredSize(new Dimension(dimX, 36));


  }

  private void setCamera() {
    int x = 0;//=(mapHeight/2)-canvasHeight/2;
    int y = 0;//=mapWidth-canvasWidth;

    Vector<PICAIUVector> vec = blueBases;
    if (imOnRedTeam) {
      vec = redBases;
    }


    x = (int) vec.firstElement().getX();
    y = (int) vec.firstElement().getY();

    screenOffX = x - canvasWidth / 2;
    screenOffY = y - canvasHeight / 2;
    //ensureScreenConstraints();
  }

  //code borrowed from:
  //http://binkley.blogspot.com/2006/06/answer-using-jformattedtextfield-for.html
  public JFormattedTextField getFormatter() {
    return new JFormattedTextField(new NumberFormatter(
            getNumberFormat()) {

      @Override
      public Object stringToValue(final String text)
              throws ParseException {
        // Throws if the getInput is corrupt from the start
        final Object parsed = super.stringToValue(text);
        final String deparsed = valueToString(parsed);

        // Throws if there is no clean roundtrip,
        // such as trailing garbage characters
        // For date parsing, etc., consider equalsIgnoreCase
        if (!deparsed.replaceAll(",", "").equals(text)) {
          throw new ParseException(text, deparsed.length());
        }

        return parsed;
      }
    }) {

      {
        // Initialize to the original starting value
        setValue(new Integer(0));
      }
    };

  }

  private NumberFormat getNumberFormat() {
    NumberFormat f = NumberFormat.getIntegerInstance();
    f.setGroupingUsed(false);
    return f;
  }

  /**
   * Begins the game engine and loops it
   */
  public void run() {
    try {
      init();
      long t1 = System.nanoTime();
      long t2;

      while (running) {
        //measure time
        t2 = System.nanoTime();
        long deltaT = ((t2 - t1) / 1000000);

        getInput();
        processInput(deltaT);


        updateGame(deltaT);
//        collisionDetection();

        //let other threads run
        long sleepTime = (long) (16.6 - (deltaT));
        if (sleepTime < 0) {
          sleepTime = 0;
        } else {
          renderGame(deltaT);
          paintScreen();
        }
        sleep(0);
//        System.out.println("sleeping for " + sleepTime + "ms");
        //yield();
        //measure time
        t1 = t2;
      }
    } catch (Exception e) {
      System.out.println(e);
    }

  }

  /**
   * Initialization code that gets run once upon starting the engine thread.
   */
  private void init() {

    if (PICAIUClient.VERBOSE) {
      System.out.println("Starting engine");
    }
    loadImages();

    grassMap = new int[(int) (mapWidth / 20) + 1][(int) (mapHeight / 20) + 1];
    //create map
    for (int i = 0; i <= (mapWidth / 20); i++) {
      for (int j = 0; j <= (mapHeight / 20); j++) {
        grassMap[i][j] = PICAIUDice.roll(5);
      }
    }

    shroudMini = new Area(new Rectangle(viewerX + 2, viewerY + 2, viewerWidth - 3, viewerHeight - 3));
    shroud = new Area(new Rectangle(-50, -50, mapWidth + 100, mapHeight + 100));

    createBases();
    setCamera();

//    createBlueTeam1();
    //createRedTeam1();


//    setNumberOfAwaitingUnits(20);
 /*   PICAIUGameUnit u1 = new PICAIUGameUnit_Gunner(true, 0, 0, 5501);
    PICAIUGameUnit u2 = new PICAIUGameUnit_Gunner(true, 0, 0, 5502);
    PICAIUGameUnit u3 = new PICAIUGameUnit_Gunner(true, 0, 0, 5503);
    PICAIUGameUnit u4 = new PICAIUGameUnit_Gunner(true, 0, 0, 5504);
    PICAIUGameUnit u5 = new PICAIUGameUnit_Tank(true, 0, 0, 5505);
    PICAIUGameUnit u6 = new PICAIUGameUnit_Gunner(true, 0, 0, 5506);
    PICAIUGameUnit u7 = new PICAIUGameUnit_Gunner(true, 0, 0, 5507);
    PICAIUGameUnit u8 = new PICAIUGameUnit_Gunner(true, 0, 0, 5508);
    PICAIUGameUnit u9 = new PICAIUGameUnit_Gunner(true, 0, 0, 5509);
    PICAIUGameUnit u10 = new PICAIUGameUnit_Tank(true, 0, 0, 5510);
    PICAIUGameUnit u11 = new PICAIUGameUnit_Gunner(true, 0, 0, 5511);
    PICAIUGameUnit u12 = new PICAIUGameUnit_Gunner(true, 0, 0, 5512);
    PICAIUGameUnit u13 = new PICAIUGameUnit_Gunner(true, 0, 0, 5513);
    PICAIUGameUnit u14 = new PICAIUGameUnit_Gunner(true, 0, 0, 5514);
    PICAIUGameUnit u15 = new PICAIUGameUnit_Tank(true, 0, 0, 5515);
    PICAIUGameUnit u16 = new PICAIUGameUnit_Gunner(true, 0, 0, 5516);
    PICAIUGameUnit u17 = new PICAIUGameUnit_Gunner(true, 0, 0, 5517);
    PICAIUGameUnit u18 = new PICAIUGameUnit_Gunner(true, 0, 0, 5518);
    PICAIUGameUnit u19 = new PICAIUGameUnit_Gunner(true, 0, 0, 5519);
    PICAIUGameUnit u20 = new PICAIUGameUnit_Tank(true, 0, 0, 5520);
    addUnitToPlace(u5);
    addUnitToPlace(u1);
    addUnitToPlace(u2);
    addUnitToPlace(u3);
    addUnitToPlace(u4);
    addUnitToPlace(u6);
    addUnitToPlace(u7);
    addUnitToPlace(u8);
    addUnitToPlace(u9);
    addUnitToPlace(u10);
    addUnitToPlace(u11);
    addUnitToPlace(u12);
    addUnitToPlace(u13);
    addUnitToPlace(u14);
    addUnitToPlace(u15);
    addUnitToPlace(u16);
    addUnitToPlace(u17);
    addUnitToPlace(u18);
    addUnitToPlace(u19);
    addUnitToPlace(u20);
     */
  }

  /**
   * Reads "res/images/animations.txt" and adds all the images into the engine
   */
  private void loadImages() {
    try {

      //initialize all the scanners and strings used for reading the file
      Scanner line, animations = new Scanner(new File("res/images/animations.txt"));
      String input, token, name;
      int numFrames, imgWidth, imgHeight;
      BufferedImage strip;

      while (animations.hasNextLine()) {
        //get getInput from file
        input = animations.nextLine();
        if (!input.startsWith("/")) {
          //line is meant to be read, so read it!
          line = new Scanner(input);

          //process all the tokens from the line!
          token = line.next();
          if (token.equals("i")) {
            name = line.next();
            optimizeAndAddImage(name, ImageIO.read(new File("res/images/" + name)));
          } else if (token.equals("s")) {
            name = line.next();
            numFrames = line.nextInt();
            strip = ImageIO.read(new File("res/images/" + name));
            imgWidth = strip.getWidth() / numFrames;
            imgHeight = strip.getHeight();

            for (int i = 0; i < numFrames; i++) {
              optimizeAndAddImage(name + "" + i, strip.getSubimage(i * imgWidth, 0, imgWidth, imgHeight));
            }

            //cut up shroudMini strip of images and add them to the engine

          }
          line.close();
        }
      }
      animations.close();

      //extra game graphics
      optimizeAndAddImage("#na#", ImageIO.read(new File("res/images/na.png")));
      optimizeAndAddImage("#grass_1#", ImageIO.read(new File("res/images/grass_1.gif")));
      optimizeAndAddImage("#grass_2#", ImageIO.read(new File("res/images/grass_2.gif")));
      optimizeAndAddImage("#grass_3#", ImageIO.read(new File("res/images/grass_3.gif")));
      optimizeAndAddImage("#grass_4#", ImageIO.read(new File("res/images/grass_4.gif")));
      optimizeAndAddImage("#grass_5#", ImageIO.read(new File("res/images/grass_5.gif")));
      optimizeAndAddImage("#def#", ImageIO.read(new File("res/images/def.gif")));
      optimizeAndAddImage("#def_1#", ImageIO.read(new File("res/images/def_1.gif")));
      optimizeAndAddImage("#def_2#", ImageIO.read(new File("res/images/def_2.gif")));
      optimizeAndAddImage("#def_3#", ImageIO.read(new File("res/images/def_3.gif")));
      optimizeAndAddImage("#off#", ImageIO.read(new File("res/images/off.gif")));
      optimizeAndAddImage("#off_1#", ImageIO.read(new File("res/images/off_1.gif")));
      optimizeAndAddImage("#off_2#", ImageIO.read(new File("res/images/off_2.gif")));
      optimizeAndAddImage("#off_3#", ImageIO.read(new File("res/images/off_3.gif")));
      optimizeAndAddImage("#health#", ImageIO.read(new File("res/images/health.gif")));
      optimizeAndAddImage("#target#", ImageIO.read(new File("res/images/target.gif")));
      optimizeAndAddImage("#redflag#", ImageIO.read(new File("res/images/redflag.gif")));
      optimizeAndAddImage("#blueflag#", ImageIO.read(new File("res/images/blueflag.gif")));

    } catch (Exception e) {
      System.out.println("Could not read images or 'animations.txt' file");
      System.out.println(e);
    }

  }

  /**
   * Polls and/or deals with user getInput as well as all other forms of asynchronous changes to
   * the client
   */
  private void getInput() {
    ConcurrentLinkedQueue events = inputQueue.getQueue();

    //go through all events
    while (!events.isEmpty()) {
      Object e = events.poll();
      //updates from server
      if (e instanceof PICAIUUnitUpdate) {
        PICAIUUnitUpdate update = (PICAIUUnitUpdate) e;
        gameObjectRefs.get(update.id).setX(update.x);
        gameObjectRefs.get(update.id).setY(update.y);
        gameObjectRefs.get(update.id).setStatus(update.status);
        gameObjectRefs.get(update.id).setSpeed(update.moveSpeed);
        gameObjectRefs.get(update.id).setTargetX(update.tx);
        gameObjectRefs.get(update.id).setTargetY(update.ty);
      } //Mouse Events
      else if (e instanceof MouseEvent) {
        if (((MouseEvent) e).getButton() == 1) {
          int x = ((MouseEvent) e).getX();
          int y = ((MouseEvent) e).getY();
          //deal with hud presses

          //mini radar
          if ((gameState == RUNNING || toPlaceUnits.isEmpty()) && bountyMenu) {
            int yy = 0;
            for (int i = 0; i < bountyBoxes; i++) {
              if (((MouseEvent) e).getX() > bountyX
                      && ((MouseEvent) e).getX() < bountyX + PICAIUBountyMenu.width
                      && ((MouseEvent) e).getY() > bountyY + yy
                      && ((MouseEvent) e).getY() < bountyY + yy + PICAIUBountyMenu.height) {
                createBounty(bountyX, bountyY, i, Integer.parseInt(bountyAmountText));
                bountyMenu = false;
              } else {
                bountyMenu = false;
              }
              yy += bountyMenuSpacing;
            }
          } else if (x > viewerX
                  && x <= viewerX + viewerWidth
                  && y > viewerY
                  && y <= viewerY + viewerHeight) {

            x -= viewerX;
            y -= viewerY;


            //adjust to proper ratio
            screenOffX = (int) (((float) x / viewerWidth) * mapWidth);
            screenOffY = (int) (((float) y / viewerHeight) * mapHeight);
            //take away half of viewer size
            screenOffX -= canvasWidth / 2;
            screenOffY -= canvasHeight / 2;


            //make sure viewer is within bounds
            ensureScreenConstraints();
            //placing troops
          } else if (gameState == PLACINGTROOPS && canPlace && !toPlaceUnits.isEmpty()) {
            PICAIUGameUnit unit = toPlaceUnits.get(placingIndex);
            if (unit != null) {
              unit.setX(x + screenOffX - gameImages.get(unit.getAnimationName()).getWidth() / 2);
              unit.setY(y + screenOffY - gameImages.get(unit.getAnimationName()).getHeight() / 2);
              if (imOnRedTeam) {
                addRedUnit(unit, unit.getId());
              } else {
                addBlueUnit(unit, unit.getId());
              }
              toPlaceUnits.remove(unit);
              if (placingIndex > toPlaceUnits.size() - 1) {
                placingIndex--;
              }
            }

            //deal with ingame presses
          } else if ((gameState == RUNNING || toPlaceUnits.isEmpty()) && !bountyMenu) {
            x += screenOffX;
            y += screenOffY;
            /*cycle through all game objects in reverse order. They have been sorted last
             * frame. This allows the objects in front to be clicked before objects behind them.
             */

            //THIS CODE BELOW ONLY FOR TESTING DURING DEVELOPMENT
            for (int i = gameObjects.size() - 1; i >= 0; i--) {
              PICAIUGameObject obj = gameObjects.get(i);
              //If the current object is selectable
              if (obj.isSelectable()) {
                Vector<PICAIUBounds> boundsVector = ((PICAIUSelectable) obj).getSelectionBounds();
                for (PICAIUBounds bounds : boundsVector) {
                  //testing square
                  if (bounds.getType() == 1) {
                    int boundsX = (int) obj.getX() + bounds.getX();
                    int boundsY = (int) obj.getY() + bounds.getY();
                    if (x > boundsX + bounds.getWidth()
                            || x < boundsX
                            || y > boundsY + bounds.getHeight()
                            || y < boundsY) {
                      //object clicked
                    } else {
                      if (obj instanceof PICAIUGameUnit && ((PICAIUGameUnit) obj).isRedTeam() == this.imOnRedTeam) {
                        long id = obj.getId();
                        if (id != -1) {
                          if (selectedId != id) {
                            viewerAnim = ((PICAIUGameUnit) obj).getMovingAnimation().clone();
                            viewerOffsetX = obj.getViewerOffsetX();
                            viewerOffsetY = obj.getViewerOffsetY();
                            selectedIdLast = selectedId;
                            inAlpha = 0f;
                            setSelectedId(id);
                            outAlpha = .25f;
                          }
                        }
                        return;
                      }
                    }
                  }
                }
              }
            }
          }
        } else if (((MouseEvent) e).getButton() == 3) {
          if (getSelectedId() != -1) {
            viewerAnim = null;
            selectedIdLast = selectedId;
            inAlpha = 0f;
            setSelectedId(-1);
            outAlpha = .125f;
          } else if (gameState == RUNNING) {
            boolean startNew = true;
            int yy = 0;
            if (bountyMenu) {
              for (int i = 0; i < bountyBoxes; i++) {
                if (((MouseEvent) e).getX() > bountyX
                        && ((MouseEvent) e).getX() < bountyX + PICAIUBountyMenu.width
                        && ((MouseEvent) e).getY() > bountyY + yy
                        && ((MouseEvent) e).getY() < bountyY + yy + PICAIUBountyMenu.height) {
                  startNew = false;
                  bountyMenu = false;
                  break;
                } else {
                }
                yy += bountyMenuSpacing;
              }
            }
            if (startNew) {
              startBounty(mx, my);
            }
          }
        }

        //keyboard events
      } else if (e instanceof KeyEvent) {


        //key pressed
        if (((KeyEvent) e).paramString().charAt(4) == 'P') {


          if (bountyMenu) {
            if (bountyStart) {
              bountyAmountText = "" + ((KeyEvent) e).getKeyChar();
              bountyStart = false;
            } else {
              bountyAmountText = bountyAmountText + "" + ((KeyEvent) e).getKeyChar();
            }

            char c = bountyAmountText.charAt(bountyAmountText.length() - 1);
            if (c != '0'
                    && c != '1'
                    && c != '2'
                    && c != '3'
                    && c != '4'
                    && c != '5'
                    && c != '6'
                    && c != '7'
                    && c != '8'
                    && c != '9') {
              bountyAmountText = bountyAmountText.substring(0, bountyAmountText.length() - 1);
            }

            if (bountyAmountText.length() > 0 && ((KeyEvent) e).getKeyCode() == 8) {
              bountyAmountText = bountyAmountText.substring(0, bountyAmountText.length() - 1);
            }

            if (bountyAmountText.length() == 0) {
              bountyAmountText = "0";
            } else {
              for (int i = 0; i < bountyAmountText.length(); i++) {
                if (bountyAmountText.charAt(i) == '0') {
                  bountyAmountText = bountyAmountText.substring(1);
                } else {
                  break;
                }
              }
            }


          }



          if (((KeyEvent) e).getKeyCode() == KeyEvent.VK_UP) {
            upPressed = true;
          }
          //pressed down
          if (((KeyEvent) e).getKeyCode() == KeyEvent.VK_DOWN) {
            downPressed = true;
          }
          //pressed left
          if (((KeyEvent) e).getKeyCode() == KeyEvent.VK_LEFT) {
            leftPressed = true;
          }
          //pressed right
          if (((KeyEvent) e).getKeyCode() == KeyEvent.VK_RIGHT) {
            rightPressed = true;
          }
          //pressed '<'
          if (((KeyEvent) e).getKeyCode() == KeyEvent.VK_COMMA) {
            lBrackPressed = true;

          }
          //pressed '>'
          if (((KeyEvent) e).getKeyCode() == KeyEvent.VK_PERIOD) {
            rBrackPressed = true;
          }
          //key released
        } else {
          if (((KeyEvent) e).getKeyCode() == KeyEvent.VK_UP) {
            upPressed = false;
          }
          //pressed down
          if (((KeyEvent) e).getKeyCode() == KeyEvent.VK_DOWN) {
            downPressed = false;
          }
          //pressed left
          if (((KeyEvent) e).getKeyCode() == KeyEvent.VK_LEFT) {
            leftPressed = false;
          }
          //pressed right
          if (((KeyEvent) e).getKeyCode() == KeyEvent.VK_RIGHT) {
            rightPressed = false;
          }

          //pressed '<'
          if (((KeyEvent) e).getKeyCode() == KeyEvent.VK_COMMA) {
            lBrackPressed = false;

          }
          //pressed '>'
          if (((KeyEvent) e).getKeyCode() == KeyEvent.VK_PERIOD) {
            rBrackPressed = false;
          }
        }
      }
    }
  }

  /**
   * Processes the user input that was gathered and acts upon it.
   */
  private void processInput(long delta) {
    float scrollSpeed = .2f * delta;

    if (upPressed) {
      screenOffY -= scrollSpeed;
    } else if (downPressed) {
      screenOffY += scrollSpeed;
    }

    if (leftPressed) {
      screenOffX -= scrollSpeed;
    } else if (rightPressed) {
      screenOffX += scrollSpeed;
    }

    ensureScreenConstraints();
  }

  /**
   * Method to make sure camera is not out of bounds
   */
  public void ensureScreenConstraints() {


    if (screenOffY < 0) {
      screenOffY = 0;
    }
    if (screenOffY > mapHeight - canvasHeight) {
      screenOffY = mapHeight - canvasHeight;
    }


    if (screenOffX < 0) {
      screenOffX = 0;
    }
    if (screenOffX > mapWidth - canvasWidth) {
      screenOffX = mapWidth - canvasWidth;
    }
  }

  /**
   * Update the objects in the game. The game logic happens here
   */
  private void updateGame(long deltaT) {

    //pre-game setup (placing troops)
    if (gameState == AWAITINGDATA) {
      if (awaitingNumber <= 0) {
        secsToPlace = maxSecsToPlace;
        gameState = PLACINGTROOPS;
      }

    } else if (gameState == PLACINGTROOPS) {
      secsToPlace -= (deltaT / 1000f);
      if (secsToPlace <= 0) {
        gameState = DRAWONCE;
        placeRestOfUnits();
      }
      checkPlacement();
      cycleUnits(deltaT);

      //loop and don't update. helps prevent a concurrency issue.
    } else if (gameState == AWAITINGENEMIES) {
      //send and receive units;
      //normal gameplay
    } else {
      for (PICAIUGameObject obj : gameObjects) {
        obj.update(deltaT);
      }

      if (viewerAnim != null) {
        viewerAnim.update(deltaT);
      }
    }

    updateBaseColor(deltaT);
  }

  private void collisionDetection() {

    //detection between moving objects
    PICAIUCollidable object1;
    PICAIUCollidable object2;

    //for all collidable objects vs all collidable objects
    for (int i = 0; i < collidables.size(); i++) {
      for (int j = i + 1; j < collidables.size(); j++) {
        //THIS CODE BELOW ONLY FOR TESTING DURING DEVELOPMENT
        object1 = collidables.get(i);
        object2 = collidables.get(j);
        Vector<PICAIUBounds> collideList = object1.getCollisionBounds();
        Vector<PICAIUBounds> collideList2 = object2.getCollisionBounds();
        for (PICAIUBounds bounds1 : collideList) {
          for (PICAIUBounds bounds2 : collideList2) {
            //if collision
            if (((PICAIUGameObject) object1).isActive() && ((PICAIUGameObject) object2).isActive() && testCollision(bounds1, (int) ((PICAIUGameObject) object1).getX(), (int) ((PICAIUGameObject) object1).getY(),
                    bounds2, (int) ((PICAIUGameObject) object2).getX(), (int) ((PICAIUGameObject) object2).getY())) {
              //collision has happened
              ((PICAIUGameUnit) object1).setAlive(false);
              ((PICAIUGameUnit) object2).setAlive(false);
              break;
            }
          }
        }
        //END DEVELOPMENT CODE
      }
    }

  }

  /**
   * Tests for shroudMini collision between to PICAIUBounds ojects. They may be of differing types.
   */
  public boolean testCollision(PICAIUBounds bounds1, int obj1x, int obj1y,
          PICAIUBounds bounds2, int obj2x, int obj2y) {
    //both must be active for this to work
    if (!bounds1.isActive() || !bounds2.isActive()) {
      return false;
    }

    short type1 = bounds1.getType();
    short type2 = bounds2.getType();

    //both bounds must have shroudMini defined shape
    if (type1 == 0 || type2 == 0) {
      return false;
    }

    int bounds1x = obj1x + bounds1.getX();
    int bounds1y = obj1y + bounds1.getY();
    int bounds2x = obj2x + bounds2.getX();
    int bounds2y = obj2y + bounds2.getY();

    //first check to see if surrounding boxes overlap. For shroudMini square/rectangle this is simply
    //enough
    //System.out.println("checking "+bounds1x+", "+bounds2x+" and "+bounds1y+", "+bounds2y);
    //System.out.println("types = ("+type1+" and "+type2+")");
    //System.out.println("heights = ("+bounds1.getHeight()+" and "+bounds2.getHeight()+")");
    //System.out.println("widths = ("+bounds1.getWidth()+" and "+bounds2.getWidth()+")");

    if (bounds1x > bounds2x + bounds2.getWidth()
            || bounds1x + bounds1.getWidth() < bounds2x
            || bounds1y > bounds2y + bounds2.getHeight()
            || bounds1y + bounds1.getHeight() < bounds2y) {
      return false;
    } else {
      if (type1 == 1 && type2 == 1) {
        return true;
      }
    }

    return false;
  }

  /**
   * Cycle through all the graphics and draw them to the back buffer
   */
  private void renderGame(long deltaT) {
    if (gameState == AWAITINGDATA || gameState == AWAITINGENEMIES) {
      return;
    }
    // do only if not initialized
    if (dbImage == null) {
      dbImage = imageOptimizer.createNewOptimizedImage((BufferedImage) frame.createImage(dimX, dimY));
      if (dbImage == null) {
        System.out.println("dbImage is null");
        return;
      } else {
        dbg = (Graphics2D) dbImage.getGraphics();

        //try different rendering hints for performance
        dbg.setRenderingHint(
                RenderingHints.KEY_ALPHA_INTERPOLATION,
                RenderingHints.VALUE_ALPHA_INTERPOLATION_SPEED);
        dbg.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_SPEED);
        dbg.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_OFF);


        dbg.setClip(-1, -1, dimX + 1, dimY + 1);
      }
    }

    //each frame

    dbg.setColor(Color.black);
    for (int i = screenOffX / 20; i <= ((screenOffX + canvasWidth) / 20); i++) {
      for (int j = screenOffY / 20; j <= ((screenOffY + canvasHeight) / 20); j++) {
        //if(i*20>screenOffX-100){
        dbg.drawImage(gameImages.get("#grass_" + grassMap[i][j] + "#"), null, canvasX + (i * 20) - screenOffX, canvasY + (j * 20) - screenOffY);

        //}
      }
    }


    if (gameState == PLACINGTROOPS) {
      displayBaseBounds(dbg);
    }

    //dbg.fillRect(10,50,canvasWidth,canvasHeight);

    sortByYAxis();
    //Game objects get rendered here... 
    for (PICAIUGameObject obj : gameObjects) {
      if (obj.isActive()) {
        BufferedImage img = null;
        if (gameImages.containsKey(obj.getAnimationName())) {
          img = gameImages.get(obj.getAnimationName());
        } else {
          img = gameImages.get("#na#");
        }

        if (obj instanceof PICAIUGameUnit) {
          if ((((PICAIUGameUnit) obj).isRedTeam() && !((PICAIUGameUnit) obj).flip()) || !((PICAIUGameUnit) obj).flip()) {
            img = FlipAnimHorizontally.flipMe(img);
          }
          satFilter.setSFactor(-.85f * (((PICAIUGameUnit) obj).getMaxHealth() - ((PICAIUGameUnit) obj).getHealth()) / (((PICAIUGameUnit) obj).getMaxHealth()));
          satFilter.setBFactor(.2f * (((PICAIUGameUnit) obj).getMaxHealth() - ((PICAIUGameUnit) obj).getHealth()) / (((PICAIUGameUnit) obj).getMaxHealth()));
        } else {
          satFilter.setSFactor(0);
          satFilter.setBFactor(0);
        }
        if (!(obj instanceof PICAIUBounty)) {
          dbg.drawImage(img, satFilter, (int) obj.getX() - screenOffX + obj.getAnimOffX(), (int) obj.getY() - screenOffY + obj.getAnimOffY());
        }

        if (obj instanceof PICAIUGameUnit && selectedId == obj.getId()) {
          target = ((PICAIUGameUnit) obj);
        } else if (selectedIdLast == obj.getId()) {
          targetXLast = obj.getTargetLockX();
          targetYLast = obj.getTargetLockY();
        }

        if (DRAWBOUNDS) {
          //draw collision bounds
          dbg.setColor(Color.red);
          if (obj instanceof PICAIUCollidable) {
            Vector<PICAIUBounds> collideList = ((PICAIUCollidable) obj).getCollisionBounds();
            for (PICAIUBounds bounds : collideList) {
              dbg.drawRect((int) (obj.getX() + bounds.getX()) - screenOffX,
                      (int) (obj.getY() + bounds.getY()) - screenOffY,
                      bounds.getWidth(),
                      bounds.getHeight());
            }
          }
          //draw selection bounds
          dbg.setColor(Color.blue);
          if (obj instanceof PICAIUSelectable) {
            Vector<PICAIUBounds> selectionList = ((PICAIUSelectable) obj).getSelectionBounds();
            for (PICAIUBounds bounds : selectionList) {
              dbg.drawRect((int) (obj.getX() + bounds.getX()) - screenOffX,
                      (int) (obj.getY() + bounds.getY()) - screenOffY,
                      bounds.getWidth(),
                      bounds.getHeight());
            }
          }

        }
      }
    }




    //draw the cursor of the selected target
    drawTarget(dbg, deltaT);

    drawShroud(dbg);
    //draw bounties
    for (PICAIUBounty b : bounties) {
      dbg.drawImage(gameImages.get(b.getAnimationName()), (int) b.getX() - screenOffX + b.getAnimOffX(), (int) b.getY() - screenOffY + b.getAnimOffY(), null);
    }

    drawPlacingTroop(dbg);
    //draw background color
    //dbg.setColor(Color.gray);
    //dbg.fillRect(0, 0, dimX, 00);
    //dbg.fillRect(0, dimY-10, dimX,00);
    //dbg.fillRect(canvasX+canvasWidth, 0, dimX-(canvasX+canvasWidth),dimY);
    //dbg.fillRect(0, 0, 1c0,dimY);

    //draw user moneys
    dbg.setColor(Color.black);
    //dbg.setFont(userMoney);
    //dbg.setColor(Color.green);
    //dbg.drawString(""+money,53,64);
    //dbg.drawString(""+enemyMoney,450,64);
    //draw user names


    //draw user names
    dbg.setFont(userData);
    dbg.setColor(Color.green);
    dbg.drawString(me, 400 - gap - ((me.length() * userData.getSize()) / 2) - me.length() - 4, 16);
    dbg.drawString(enemy, 400, 16);



    Composite c = dbg.getComposite();
    dbg.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, .6f));
    //draw flags
    dbg.drawImage(gameImages.get("#blueflag#"), null,
            blueFlagX, blueFlagY);
    dbg.drawString("" + blueCount, blueFlagX - 80 + 48 - ((("" + blueCount).length() * userData.getSize()) / 2) - ("" + blueCount).length(), blueFlagY + 20);
    dbg.drawImage(gameImages.get("#redflag#"), null,
            redFlagX, redFlagY);
    dbg.drawString("" + redCount, redFlagX + 80, redFlagY + 20);



    //draw selected guy window
    dbg.drawRect(viewerX, viewerY, viewerWidth, viewerHeight);
    dbg.drawRect(viewerX + 1, viewerY + 1, viewerWidth - 2, viewerHeight - 2);
    dbg.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, .2f));
    dbg.fillRect(viewerX + 2, viewerY + 2, viewerWidth - 3, viewerHeight - 3);
    dbg.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, .6f));

    if (false && viewerAnim != null) {
      PICAIUGameUnit unit = ((PICAIUGameUnit) gameObjectRefs.get(selectedId));
      dbg.drawImage(gameImages.get(viewerAnim.getAnimation()),
              null, viewerX + viewerOffsetX, viewerY + viewerOffsetY);

      /*for(int i = 0; i<unit.getDefense();i++){
      dbg.drawImage(gameImages.get("#def#"),null,viewerX+(i*8)+4,viewerY+66);
      }

      for (int i = 0; i < unit.getOffense(); i++) {
      dbg.drawImage(gameImages.get("#off#"), null, viewerX + (i * 8) + 4, viewerY + 77);
      }

      /*for(int i = 0; i<unit.getHealthUpgrade();i++){
      dbg.drawImage(gameImages.get("#health#"),null,viewerX+(i*8)+4,viewerY+88);
      }*/
      //draw rader hud
    } else {


      float scaleX = ((float) canvasWidth / mapWidth);
      float scaleY = ((float) canvasHeight / mapHeight);
      float startX;//=((float)screenOffX/mapWidth);
      float startY;//=((float)screenOffY/mapWidth);


      dbg.setColor(Color.CYAN);
      Vector<PICAIUGameUnit> units = blueUnits;
      Vector<PICAIUGameUnit> enemyUnits = redUnits;
      Color eu = Color.red;
      if (imOnRedTeam) {
        units = redUnits;
        enemyUnits = blueUnits;
        eu = Color.CYAN;
        dbg.setColor(Color.RED);
      }


      for (PICAIUGameUnit u : units) {
        if (u.isAlive()) {
          startX = ((float) u.getCenterX() / mapWidth);
          startY = ((float) u.getCenterY() / mapHeight);
          dbg.drawRect(viewerX + 1 + (int) (startX * viewerWidth), viewerY + 1 + (int) (startY * viewerHeight), 1, 1);
          float visionX = (u.getVision() * scaleX) / 10;
          float visionY = (u.getVision() * scaleY) / 7;
          shroudMini.subtract(new Area(new Ellipse2D.Float(viewerX + 1 + (startX * viewerWidth) - visionX / 2, viewerY + 1 + (startY * viewerHeight) - visionY / 2, visionX, visionY)));
        }
      }

      dbg.setColor(eu);
      for (PICAIUGameUnit u : enemyUnits) {
        if (u.isAlive() && !shroud.contains(u.getCenterX(), u.getCenterY())) {
          startX = ((float) u.getCenterX() / mapWidth);
          startY = ((float) u.getCenterY() / mapHeight);
          dbg.drawRect(viewerX + 1 + (int) (startX * viewerWidth), viewerY + 1 + (int) (startY * viewerHeight), 1, 1);
        }
      }


      //draw bounties on map
      dbg.setColor(Color.white);
      for (PICAIUBounty b : bounties) {
        startX = ((float) b.getCenterX() / mapWidth);
        startY = ((float) b.getCenterY() / mapHeight);
        if (b.isAttached()) {
          startX = ((float) b.getAttachedUnit().getCenterX() / mapWidth);
          startY = ((float) (b.getAttachedUnit().getCenterY() - 30) / mapHeight);
        }
        dbg.drawRect(viewerX + 1 + (int) (startX * viewerWidth), viewerY + 1 + (int) (startY * viewerHeight), 1, 1);
      }



      dbg.setColor(Color.black);
      dbg.fill(shroudMini);

      dbg.setColor(Color.white);
      startX = ((float) screenOffX / mapWidth);
      startY = ((float) screenOffY / mapHeight);
      dbg.drawRect(viewerX + 1 + (int) (startX * viewerWidth), viewerY + 1 + (int) (startY * viewerHeight), (int) (scaleX * viewerWidth) - 2, (int) (scaleY * viewerHeight) - 2);
    }
    dbg.setComposite(c);


    drawBountyMenu(dbg);

    //draw hud text
    if (gameState == PLACINGTROOPS) {
      String place = "Place your Units";
      dbg.drawString(place, (canvasWidth / 2) - (place.length() / 4 * dbg.getFont().getSize()) - place.length() / 4, 90);
      String seconds = "" + (int) secsToPlace;
      if (secsToPlace < 10) {
        seconds = "0" + seconds;
      }
      dbg.drawString(seconds, (canvasWidth / 2) - (seconds.length() / 2 * dbg.getFont().getSize()) - seconds.length() / 2 - 2, 106);

      displayTroopsToPlace(dbg);
    } else if (gameState == DRAWONCE) {
      String place = "Awaiting Enemy..";
      dbg.drawString(place, (canvasWidth / 2) - (place.length() / 4 * dbg.getFont().getSize()) - place.length() / 4, 90);
      setFinishedPlacing();
    }

  }

  /*
   * Indicates that all units have been placed and the battle may commence begin the next phase.
   */
  private void setFinishedPlacing() {
    Vector<PICAIUGameUnit> units = blueUnits;
    if (imOnRedTeam) {
      units = redUnits;
    }
    gui.getClient().sendPositions(units);
    gameState = AWAITINGENEMIES;
  }

  /*
   * This method draws the shroud
   */
  private void drawShroud(Graphics2D dbg) {
    //for(int i = 0;i<canvasHeight;i+=16){
    //if(!shroud[i/16][0]){
//        dbg.drawImage(gameImages.get("#shroud#"),null,7,7);
    //}

    //}
    dbg.setColor(Color.black);


    Vector<PICAIUGameUnit> units = null;
    if (imOnRedTeam) {
      units = redUnits;
    } else {
      units = blueUnits;
    }

    for (PICAIUGameUnit u : units) {
      shroud.subtract(new Area(new Ellipse2D.Float(u.getCenterX() - u.getVision() / 2, u.getCenterY() - u.getVision() / 2, u.getVision(), u.getVision())));
    }


//      dbg.fill(shroudMini);
    //   shroud.transform(at);
    Area a = (Area) shroud.clone();
    a.intersect(new Area(new Rectangle(screenOffX - 50, screenOffY - 50, canvasWidth + 100, canvasHeight + 100)));
    AffineTransform at = new AffineTransform();
    at.translate(-screenOffX, -screenOffY);
    a.transform(at);


    int shadowWidth = 3;
    for (int i = 1; i < shadowWidth + 1; i++) {
      //float pct = (float)(sw - i) / (sw - 1);
      //System.out.println(pct);
      dbg.setColor(new Color(0, 0, 0, .5f));
      Shape sh = new BasicStroke((i * i * 10) + 10, BasicStroke.CAP_BUTT, BasicStroke.JOIN_ROUND).createStrokedShape(a);
      //dbg.setClip(darknessInvert);
      //TexturePaint tp = new TexturePaint(this.bufferedImages.get("stiple4"), new Rectangle2D.Float(0,0,3*i,3*i));
      //dbg.setPaint(tp);
      dbg.fill(sh);
      //dbg.setPaint(null);

      //dbg.setClip(null);
    }
    //dbg.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
    //        RenderingHints.VALUE_ANTIALIAS_OFF);
    dbg.setStroke(new BasicStroke(0));
    dbg.setColor(new Color(0, 0, 0));
    dbg.fill(a);
  }

  /**
   * This method draws the currently selected target's cursor
   */
  private void drawTarget(Graphics2D dbg, long deltaT) {

    //draw target
    dbg.setColor(Color.green);
    float alphaRate = .005f * (deltaT);

    if (inAlpha + alphaRate < 1f) {
      inAlpha += alphaRate;
    } else {
      inAlpha = 1f;
    }


    if (outAlpha - alphaRate >= 0f) {
      outAlpha -= alphaRate;
    } else {
      outAlpha = 0;
      selectedIdLast = -2;
    }

    Composite c = dbg.getComposite();
    //last selected unit
    if (selectedIdLast > -1) {
      dbg.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, outAlpha));
      //y lines
      dbg.drawRect(targetXLast + 12 - screenOffX, -screenOffY, 1, targetYLast + 1);
      dbg.drawRect(targetXLast + 12 - screenOffX, targetYLast + 22 - screenOffY, 1, canvasY + mapHeight - (targetYLast + 24));
      //x lines
      dbg.drawRect(canvasX - screenOffX, targetYLast + 12 - screenOffY, targetXLast - canvasX + 1, 1);
      dbg.drawRect(canvasX + targetXLast + 12 - screenOffX, targetYLast + 12 - screenOffY, mapWidth + canvasX - (targetXLast + 23), 1);
      dbg.drawImage(gameImages.get("#target#"), null,
              targetXLast, targetYLast);

    }


    if (target != null) {
      int targetX = target.getTargetLockX();
      int targetY = target.getTargetLockY();
      //currently selected unit
      if (selectedId > -1) {
        dbg.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, (float) (inAlpha / 4)));
        //y lines
        dbg.drawRect(targetX + 12 - screenOffX, -screenOffY, 1, targetY + 1);
        dbg.drawRect(targetX + 12 - screenOffX, targetY + 22 - screenOffY, 1, canvasY + mapHeight - (targetY + 24));
        //x lines
        dbg.drawRect(canvasX - screenOffX, targetY + 12 - screenOffY, targetX - canvasX + 1, 1);
        dbg.drawRect(canvasX - screenOffX + targetX + 12, targetY + 12 - screenOffY, mapWidth + canvasX - (targetX + 23), 1);

        dbg.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, (float) (inAlpha / 4)));
        dbg.drawImage(gameImages.get("#target#"), null,
                targetX - screenOffX, targetY - screenOffY);


        dbg.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, inAlpha));
        //draw defense
        /*if(target.getDefense()>0){
        dbg.drawImage(gameImages.get("#def_"+target.getDefense()+"#"),null,canvasX+targetX-2-screenOffX,targetY-3-screenOffY);
        }
        //draw offense
        if (target.getOffense() > 0) {
        dbg.drawImage(gameImages.get("#off_" + target.getOffense() + "#"), null, canvasX + targetX - 2 - screenOffX, targetY + 14 - screenOffY);
        }

        /*for(int i = 0; i<target.getHealthUpgrade();i++){
        dbg.drawImage(gameImages.get("#health#"),null,canvasX+targetX+24+(i*9)-screenOffX,targetY+4-screenOffY);
        }*/
        //draw health


        dbg.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, inAlpha));


      }


    }
    dbg.setComposite(c);
  }

  /**
   * Sorts the objects by their y-axis location. The top and bottom of the y "bounds" are considered
   */
  private void sortByYAxis() {
    for (int i = 0; i < gameObjects.size() - 1; i++) {
      if (gameObjects.get(i).getYMiddle() > gameObjects.get(i + 1).getYMiddle()) {
        PICAIUGameObject s = gameObjects.get(i);
        PICAIUGameObject t = gameObjects.get(i + 1);
        gameObjects.set(i, t);
        gameObjects.set(i + 1, s);
      }
    }

  }

  /**
   * This method paints the back buffer to the screen for the user to view.
   */
  private void paintScreen() {
    Graphics2D g;
    try {
      g = (Graphics2D) frame.getGraphics();
      if ((g != null) && (dbImage != null)) {
        g.drawImage(dbImage, 0, 0, null);
        g.dispose();
      }
    } catch (Exception e) {
      System.out.println("Graphics context error: " + e);
    }
  }

  /**
   * This method switches the clients main container to this GUI's graphics.
   */
  public void setAsGUI() {
    frameParent.removeAll();
    frameParent.validate();
    frameParent.add(frame, BorderLayout.CENTER);
    //frameParent.add(bottom, BorderLayout.SOUTH);
    Thread t = new Thread(this);
    t.start();
  }

  /**
   * A synchronized method to create and add objects to the game engine. May be called from
   * server so synchronization is likely shroudMini good idea.
   */
  public void addPICAIUGameObject(PICAIUGameObject object, long id) {
    gameObjects.add(object);
    gameObjectRefs.put(id, object);
  }

  /**
   * A synchronized method to create and add collidable gameobjects to the game engine.
   * These objects may collide with each other. Walls may end up here
   * May be called from server so synchronization is likely shroudMini good idea.
   */
  public void addCollidablePICAIUGameObject(PICAIUCollidable object, long id) {
    addPICAIUGameObject((PICAIUGameObject) object, id);
    collidables.add(object);
  }

  /**
   * Adds shroudMini unit to the red team. It also assumes it is shroudMini collidable unit (all soldiers and tanks
   * are) and adds it to the collidables list, as well as adds it to the overall list of game
   * objects.
   */
  public void addRedUnit(PICAIUGameUnit object, long id) {
    redUnits.add(object);
    gameObjects.add(object);
    gameObjectRefs.put(id, object);
    object.setActive(true);
    //object.setDefense(PICAIUDice.roll(4)-1);
    //object.setOffense(PICAIUDice.roll(4) - 1);
    //object.setVision(PICAIUDice.roll(240) + 240);
    //object.setAccuracy(PICAIUDice.roll(5) + 3);
    //object.setEnergy(PICAIUDice.roll(5) + 3);
    //object.setHealthUpgrade(PICAIUDice.roll(4)-1);
    object.setHealth(100);
    addCollidablePICAIUGameObject((PICAIUCollidable) object, id);
    redCount++;
  }

  /**
   * Adds shroudMini unit to the blue team. It also assumes it is shroudMini collidable unit (all soldiers and tanks
   * are) and adds it to the collidables list, as well as adds it to the overall list of game
   * objects.
   */
  public void addBlueUnit(PICAIUGameUnit object, long id) {
    blueUnits.add(object);
    gameObjectRefs.put(id, object);
    gameObjects.add(object);
    object.setActive(true);
    //object.setDefense(PICAIUDice.roll(4)-1);
    //object.setOffense(PICAIUDice.roll(4) - 1);
    //object.setVision(PICAIUDice.roll(240) + 240);
    //object.setAccuracy(PICAIUDice.roll(5) + 3);
    //object.setEnergy(PICAIUDice.roll(5) + 3);
    //object.setHealthUpgrade(PICAIUDice.roll(4)-1);
    object.setHealth(100);
    addCollidablePICAIUGameObject((PICAIUCollidable) object, id);
    blueCount++;
  }

  /**
   * Creates shroudMini new Gunner and makes him red and puts him on the red team
   */
  public void addRedGunner(int xIn, int yIn, long id) {
    addRedUnit(new PICAIUGameUnit_Gunner(true, xIn, yIn, id), id);
  }

  /**
   * Creates shroudMini new gunner and makes him blue and puts him on the blue team
   */
  public void addBlueGunner(int xIn, int yIn, long id) {
    addBlueUnit(new PICAIUGameUnit_Gunner(false, xIn, yIn, id), id);
  }

  /**
   * Creates shroudMini new Tank and makes him red and puts him on the red team
   */
  public void addRedTank(int xIn, int yIn, long id) {
    addRedUnit(new PICAIUGameUnit_Tank(true, xIn, yIn, id), id);
  }

  /**
   * Creates shroudMini new Tank and makes him blue and puts him on the blue team
   */
  public void addBlueTank(int xIn, int yIn, long id) {
    addBlueUnit(new PICAIUGameUnit_Tank(false, xIn, yIn, id), id);
  }

  /**
   * Returns the red units. This can be used as percepts for unit AI
   */
  public Vector<PICAIUGameUnit> getRedUnits() {
    return redUnits;
  }

  /**
   * Returns the blue units. This can be used as percepts for unit AI
   */
  public Vector<PICAIUGameUnit> getBlueUnits() {
    return blueUnits;
  }

  /**
   * Optimizes and add shroudMini new BufferedImage. This overwrites any previous image
   * which already exists in this running version of the game engine.
   * @param name name of image used to store it. Must be unique.
   * @param image the BufferedImage to be optimized and then added
   */
  public void optimizeAndAddImage(String name, BufferedImage image) {
    gameImages.put(name, PICAIUJava2DImageOptimizer.createNewOptimizedImage(image));
  }

  /**
   * Add the given InputEvent to the inputQueue that the game uses to process getInput. Objects
   * are dealt with in shroudMini FIFO manner.
   */
  public void addToInputQueue(InputEvent e) {
    inputQueue.add(e);
  }

  /**
   * Sets the currently selected GameUnit by their given id.
   */
  public void setSelectedId(long id) {
    selectedId = id;
  }

  /**
   * Gets the currently selected GameUnit by their given id.
   */
  public long getSelectedId() {
    return selectedId;
  }

  /**
   * Set's the number of units that the system is waiting on to be placed by player
   * @param num
   */
  public void setNumberOfAwaitingUnits(int num) {
    awaitingNumber = num;
  }

  /**
   * Adds unit to be placed by player
   * @param u
   */
  public void addUnitToPlace(PICAIUGameUnit u) {
    toPlaceUnits.add(u);
  }

  /**
   * Sets up team 1 for red team
   */
  private void createRedTeam1() {


    addRedTank(430, 108, 1);
    addRedTank(430, 138, 2);
    addRedTank(480, 108, 3);
    addRedTank(480, 138, 4);

    addRedGunner(440, 310, 5);
    addRedGunner(440, 185, 6);
    addRedGunner(440, 210, 7);
    addRedGunner(440, 235, 8);
    addRedGunner(440, 260, 9);
    addRedGunner(440, 285, 10);
    addRedGunner(440, 310, 11);

    addRedGunner(470, 310, 12);
    addRedGunner(470, 185, 13);
    addRedGunner(470, 210, 14);
    addRedGunner(470, 235, 15);
    addRedGunner(470, 260, 16);
    addRedGunner(470, 285, 17);
    addRedGunner(470, 310, 18);

    addRedTank(1830, 338, 19);
    addRedTank(1830, 368, 20);
    addRedTank(1880, 338, 21);
    addRedTank(1880, 368, 22);


  }

  /**
   * Loads into the memory the bases for each player
   */
  private void createBases() {
    blueBases.add(new PICAIUVector(180, mapHeight - 180));
    blueBases.add(new PICAIUVector(780, mapHeight - 180));
    blueBases.add(new PICAIUVector(1400, mapHeight - 180));
    blueBases.add(new PICAIUVector(180, mapHeight - 800));


    redBases.add(new PICAIUVector(mapWidth - 180, 180));
    redBases.add(new PICAIUVector(mapWidth - 780, 180));
    redBases.add(new PICAIUVector(mapWidth - 1400, 180));
    redBases.add(new PICAIUVector(mapWidth - 180, 800));

    bases.addAll(blueBases);
    bases.addAll(redBases);


    float scaleX = ((float) canvasWidth / mapWidth);
    float scaleY = ((float) canvasHeight / mapHeight);
    float startX;//=((float)screenOffX/mapWidth);
    float startY;//=((float)screenOffY/mapWidth);

    Vector<PICAIUVector> vec = blueBases;
    if (imOnRedTeam) {
      vec = redBases;
    }


    for (PICAIUVector v : vec) {
      startX = ((float) v.getX() / mapWidth);
      startY = ((float) v.getY() / mapHeight);
      float visionX = (baseVision * scaleX) / 8;
      float visionY = (baseVision * scaleY) / 5.5f;
      shroud.subtract(new Area(new Ellipse2D.Float((float) v.getX() - baseVision / 2, (float) v.getY() - baseVision / 2, baseVision, baseVision)));
      shroudMini.subtract(new Area(new Ellipse2D.Float(viewerX + 1 + (startX * viewerWidth) - visionX / 2, viewerY + 1 + (startY * viewerHeight) - visionY / 2, visionX, visionY)));
    }

  }

  /**
   * Sets up team 1 for blue team
   */
  private void createBlueTeam1() {

    addBlueTank(80, 108, 51);
    addBlueTank(80, 138, 52);
    addBlueTank(1030, 108, 53);
    addBlueTank(1030, 138, 54);

    addBlueGunner(1310, 310, 55);
    addBlueGunner(1310, 185, 56);
    addBlueGunner(1410, 210, 57);
    addBlueGunner(1410, 235, 58);
    addBlueGunner(110, 260, 59);
    addBlueGunner(1710, 285, 60);
    addBlueGunner(1710, 310, 61);

    addBlueGunner(140, 310, 62);
    addBlueGunner(140, 185, 63);
    addBlueGunner(140, 210, 64);
    addBlueGunner(140, 235, 65);
    addBlueGunner(140, 260, 66);
    addBlueGunner(140, 285, 67);
    addBlueGunner(140, 310, 68);

    addBlueTank(80, 338, 69);
    addBlueTank(80, 368, 70);
    addBlueTank(130, 338, 71);
    addBlueTank(130, 368, 72);

  }

  /**
   * This method displays all units which are to be placed
   */
  private void displayTroopsToPlace(Graphics2D dbg) {
    int y = 4;
    int x = 4;
    Composite c = dbg.getComposite();

    if (toPlaceUnits.isEmpty()) {
      return;
    }

    for (PICAIUGameUnit u : toPlaceUnits) {
      //set all but the current guy to appear faded
      if (placingIndex == toPlaceUnits.indexOf(u)) {
        dbg.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, .85f));
      } else {
        dbg.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, .33f));
      }

      BufferedImage img = gameImages.get(u.getAnimationName());
      //draw guys
      if (u.isRedTeam()) {
        img = FlipAnimHorizontally.flipMe(img);
      }
      dbg.drawImage(img, x, y, null);
      if (u instanceof PICAIUGameUnit_Gunner) {
        y += 24;
      } else if (u instanceof PICAIUGameUnit_Tank) {
        y += 40;
      }

      if (y > canvasHeight / 2 + 64) {
        y = 4;
        x += 30;
      }
    }
    dbg.setComposite(c);
  }

  private void displayBaseBounds(Graphics2D dbg) {


    int placement = baseVision - 100;
    dbg.setColor(new Color(.5f + baseColor, .5f + baseColor, .5f + baseColor, .1f));
    Composite c = dbg.getComposite();
    for (PICAIUVector v : bases) {
      dbg.fillOval((int) v.getX() - placement / 2 - screenOffX, (int) v.getY() - placement / 2 - screenOffY, placement, placement);
      //dbg.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, .33f));
      //   dbg.fillOval((int) v.getX() - placement / 2 - screenOffX, (int) v.getY() - placement / 2 - screenOffY, placement, placement);
      // dbg.setComposite(c);
    }
  }

  public void mouseDragged(MouseEvent e) {
  }

  public void mouseMoved(MouseEvent e) {
    mx = e.getX();
    my = e.getY();
  }

  /**
   * Draw's the currently selected soldier to be placed
   * @param dbg
   */
  private void drawPlacingTroop(Graphics2D dbg) {
    if (toPlaceUnits.isEmpty()) {
      return;
    }

    if (gameState == PLACINGTROOPS) {
      BufferedImage img = gameImages.get(toPlaceUnits.get(placingIndex).getAnimationName());
      //draw guys
      if (toPlaceUnits.get(placingIndex).isRedTeam()) {
        img = FlipAnimHorizontally.flipMe(img);
      }
      Composite c = dbg.getComposite();
      if (!canPlace) {
        satFilter.setHFactor(1);
        satFilter.setSFactor(-1);
        satFilter.setBFactor(-.4f);
        dbg.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, .6f));
        dbg.drawImage(img, satFilter, mx - img.getWidth() / 2, my - img.getHeight() / 2);
      } else {
        dbg.drawImage(img, mx - img.getWidth() / 2, my - img.getHeight() / 2, null);
      }

      dbg.setComposite(c);

    }
  }

  private void checkPlacement() {

    canPlace = false;
    Vector<PICAIUGameUnit> units = blueUnits;
    if (imOnRedTeam) {
      units = redUnits;
    }
    for (PICAIUVector v : bases) {
      double a, b, c;
      a = mx + screenOffX - v.getX();
      b = my + screenOffY - v.getY();
      c = (baseVision - 100) / 2;
      if (a * a + b * b < c * c) {
        boolean boundsOk = true;
        for (PICAIUGameUnit u : units) {
          if (!toPlaceUnits.isEmpty()) {
            for (PICAIUBounds bound : u.getCollisionBounds()) {
              for (PICAIUBounds bound2 : toPlaceUnits.get(placingIndex).getCollisionBounds()) {
                int xc = gameImages.get(toPlaceUnits.get(placingIndex).getAnimationName()).getWidth() / 2;
                int yc = gameImages.get(toPlaceUnits.get(placingIndex).getAnimationName()).getHeight() / 2;
                if (this.testCollision(bound, (int) u.getX(), (int) u.getY(), bound2, mx + screenOffX - xc, my + screenOffY - yc)) {
                  boundsOk = false;
                  break;
                }
              }
            }
          }
        }
        if (boundsOk) {
          canPlace = true;
          break;
        }
      }
    }
  }

  private boolean checkPlacement2(PICAIUGameUnit uIn) {

    canPlace = false;
    Vector<PICAIUGameUnit> units = blueUnits;
    if (imOnRedTeam) {
      units = redUnits;
    }
    for (PICAIUVector v : bases) {
      double a, b, c;
      a = mx + screenOffX - v.getX();
      b = my + screenOffY - v.getY();
      c = (baseVision - 100) / 2;
      if (a * a + b * b < c * c) {
        boolean boundsOk = true;
        for (PICAIUGameUnit u : units) {
          if (!toPlaceUnits.isEmpty()) {
            for (PICAIUBounds bound : u.getCollisionBounds()) {
              for (PICAIUBounds bound2 : uIn.getCollisionBounds()) {
                int xc = gameImages.get(uIn.getAnimationName()).getWidth() / 2;
                int yc = gameImages.get(uIn.getAnimationName()).getHeight() / 2;
                if (this.testCollision(bound, (int) u.getX(), (int) u.getY(), bound2, mx + screenOffX - xc, my + screenOffY - yc)) {
                  boundsOk = false;
                  break;
                }
              }
            }
          }
        }
        if (boundsOk) {
          canPlace = true;
          break;
        }
      }
    }
    return canPlace;
  }

  private void cycleUnits(long deltaT) {

    if (cycleTime > 0) {
      cycleTime -= deltaT;
    } else {
      if (rBrackPressed) {
        placingIndex++;
        if (placingIndex > toPlaceUnits.size() - 1) {
          placingIndex = 0;
        }
        cycleTime = cycleTimeReset;
      }
      if (lBrackPressed) {
        placingIndex--;
        if (placingIndex < 0) {
          placingIndex = toPlaceUnits.size() - 1;
        }
        cycleTime = cycleTimeReset;
      }
    }
  }

  private void startBounty(int mx, int my) {

    Vector<PICAIUGameUnit> units = null;


    if (imOnRedTeam) {
      units = blueUnits;
    } else {
      units = redUnits;
    }


    attachedUnit = -1;
    bountyBoxes = 2;
    for (PICAIUGameUnit obj : units) {
      if (obj.isSelectable() && !shroud.contains(mx + screenOffX, my + screenOffY)) {
        Vector<PICAIUBounds> boundsVector = ((PICAIUSelectable) obj).getSelectionBounds();
        for (PICAIUBounds bounds : boundsVector) {
          //testing square
          if (bounds.getType() == 1) {
            int boundsX = (int) obj.getX() + bounds.getX();
            int boundsY = (int) obj.getY() + bounds.getY();
            if (mx + screenOffX > boundsX + bounds.getWidth()
                    || mx + screenOffX < boundsX
                    || my + screenOffY > boundsY + bounds.getHeight()
                    || my + screenOffY < boundsY) {
              //object clicked
            } else {
              attachedUnit = obj.getId();
              bountyBoxes = 1;
              break;
            }
          }
        }
      }
    }

    bountyMenu = true;
    bountyStart = true;
    bountyX = mx + 2;
    bountyY = my;
  }

  private void createBounty(int mx, int my, int type, int value) {

    if (value == 0) {
      return;
    }


    PICAIUBounty b = null;
    if (attachedUnit == -1) {
      b = new PICAIUBounty(mx + screenOffX, my + screenOffY, type, value);
    } else {
      b = new PICAIUBounty((PICAIUGameUnit) gameObjectRefs.get(attachedUnit), value);
    }

    bounties.add(b);
    gameObjects.add(b);
    String team = "B";
    if(imOnRedTeam){
      team = "R";
    }
    gui.getClient().sendBounty(team,(int) (mx + screenOffX), (int) (my + screenOffY), type, value, attachedUnit);
  }

  private void drawBountyMenu(Graphics2D dbg) {
    if (bountyMenu) {
      int y = 0;
      Font f = dbg.getFont();

      Font f1 = dbg.getFont().deriveFont(12f);
      dbg.setFont(f1);
      dbg.setColor(Color.green);
      dbg.drawString(bountyAmountText, bountyX, bountyY - 2);

      Font f2 = dbg.getFont().deriveFont(12f);
      dbg.setFont(f2);

      //there isn't a guy attached to this command
      if (attachedUnit == -1) {
        for (int i = 0; i < bountyBoxes; i++) {
          dbg.setColor(PICAIUBountyMenu.color);
          dbg.drawRect(bountyX, bountyY + y, PICAIUBountyMenu.width, PICAIUBountyMenu.height);
          dbg.drawRect(bountyX + 1, bountyY + y + 1, PICAIUBountyMenu.width - 2, PICAIUBountyMenu.height - 2);

          boolean selected = false;
          if (mx > bountyX
                  && mx < bountyX + PICAIUBountyMenu.width
                  && my > bountyY + y
                  && my < bountyY + y + PICAIUBountyMenu.height) {
            selected = true;
          } else {
            selected = false;
          }

          if (selected) {
            dbg.setColor(PICAIUBountyMenu.selected);
          } else {
            dbg.setColor(PICAIUBountyMenu.bgcolor);
          }
          dbg.fillRect(bountyX + 2, bountyY + y + 2, PICAIUBountyMenu.width - 3, PICAIUBountyMenu.height - 3);

          if (selected) {
            dbg.setColor(PICAIUBountyMenu.textselected);
          } else {
            dbg.setColor(PICAIUBountyMenu.textcolor);
          }
          dbg.drawString(bountyMenus[i].toString(), bountyX + 2, bountyY + y + 12);
          y += bountyMenuSpacing;
        }
      } else {

        dbg.setColor(PICAIUBountyMenu.color);
        dbg.drawRect(bountyX, bountyY + y, PICAIUBountyMenu.width, PICAIUBountyMenu.height);
        dbg.drawRect(bountyX + 1, bountyY + y + 1, PICAIUBountyMenu.width - 2, PICAIUBountyMenu.height - 2);

        boolean selected = false;
        if (mx > bountyX
                && mx < bountyX + PICAIUBountyMenu.width
                && my > bountyY + y
                && my < bountyY + y + PICAIUBountyMenu.height) {
          selected = true;
        } else {
          selected = false;
        }

        if (selected) {
          dbg.setColor(PICAIUBountyMenu.selected);
        } else {
          dbg.setColor(PICAIUBountyMenu.bgcolor);
        }
        dbg.fillRect(bountyX + 2, bountyY + y + 2, PICAIUBountyMenu.width - 3, PICAIUBountyMenu.height - 3);

        if (selected) {
          dbg.setColor(PICAIUBountyMenu.textselected);
        } else {
          dbg.setColor(PICAIUBountyMenu.textcolor);
        }

        dbg.drawString(bountyMenus[2].toString(), bountyX + 2, bountyY + y + 12);


      }
      dbg.setFont(f);
    }
  }

  public void setEnemyMoney(int money) {
    enemyMoney = money;
  }

  private void updateBaseColor(long deltaT) {

    if (baseAdd) {
      baseColor += deltaT / 1000f;
      if (baseColor >= .3f) {
        baseColor = .3f;
        baseAdd = false;
      }
    } else {
      baseColor -= deltaT / 1000f;
      if (baseColor <= -.3f) {
        baseColor = -.3f;
        baseAdd = true;
      }
    }

  }

  public void addIncomingUnit(String unit) {
    String[] data = unit.split("#");

    //which color is unit
    boolean redUnit = false;
    if (data[1].equals("R")) {
      redUnit = true;
    }

    //is unit on my team?
    boolean myTeam = false;
    if (redUnit && imOnRedTeam) {
      myTeam = true;
    } else if (!redUnit && !imOnRedTeam) {
      myTeam = true;
    }

    //create unit
    int type = Integer.parseInt(data[3]);
    long id = Long.parseLong(data[2]);
    PICAIUGameUnit u = null;
    //gunner
    if (type == 0) {
      u = new PICAIUGameUnit_Gunner(redUnit, mapWidth + 1000, mapHeight + 1000, id);
      //tank
    } else if (type == 1) {
      u = new PICAIUGameUnit_Tank(redUnit, mapWidth + 1000, mapHeight + 1000, id);
      //rocketeer
    } else if (type == 2) {
    }
    u.setVision(Integer.parseInt(data[4]));

    if (myTeam) {
      addUnitToPlace(u);
    } else if (redUnit) {
      addRedUnit(u, id);
    } else if (!redUnit) {
      addBlueUnit(u, id);
    }
    awaitingNumber--;
    //System.out.println(unit);
    //awaitingNumber--;
  }

  public void setRedTeam(boolean redTeam) {
    imOnRedTeam = redTeam;
  }

  public void setInitialEnemyPositions(String team, long id, float x, float y) {
    this.gameObjectRefs.get(id).setX(x);
    this.gameObjectRefs.get(id).setY(y);
    retrieved++;
    if ((team.equals("R") && retrieved == redUnits.size())
            || (team.equals("B") && retrieved == blueUnits.size())) {
      gui.setInitialTroopsRetrieved();
    }
  }

  public void startEngine() {
    gameState = RUNNING;
  }

  private void placeRestOfUnits() {
    /*double x = 0, y = 0;

    HashMap<Integer, PICAIUVector> locs = new HashMap<Integer, PICAIUVector>();
    int l = 0;
    for (int i = (-45 * 9); i <= (45 * 9); i += 45) {
    for (int j = (-45 * 9); j <= (45 * 9); j += 45) {
    locs.put(l++, new PICAIUVector(i, j));
    }
    }


    if (imOnRedTeam) {
    x = redBases.get(PICAIUDice.roll(redBases.size()) - 1).x;
    y = redBases.get(PICAIUDice.roll(redBases.size()) - 1).y;
    } else {
    x = blueBases.get(PICAIUDice.roll(blueBases.size()) - 1).x;
    y = blueBases.get(PICAIUDice.roll(blueBases.size()) - 1).y;
    }

    PICAIUVector v;

    int count = 0;

    for (PICAIUGameUnit unit : toPlaceUnits) {
    Iterator i = locs.keySet().iterator();
    v = (PICAIUVector) locs.get(i.next());
    unit.setX(v.getX() + x - gameImages.get(unit.getAnimationName()).getWidth() / 2);
    unit.setY(v.getY() + y - gameImages.get(unit.getAnimationName()).getHeight() / 2);
    while (!checkPlacement2(unit)) {
    if(!i.hasNext()){
    i = locs.keySet().iterator();
    }
    v = (PICAIUVector) locs.get(i.next());
    unit.setX(v.getX() + x - gameImages.get(unit.getAnimationName()).getWidth() / 2);
    unit.setY(v.getY() + y - gameImages.get(unit.getAnimationName()).getHeight() / 2);
    count++;
    if (count == 5) {
    count = 0;
    if (imOnRedTeam) {
    x = redBases.get(PICAIUDice.roll(redBases.size()) - 1).x;
    y = redBases.get(PICAIUDice.roll(redBases.size()) - 1).y;
    } else {
    x = blueBases.get(PICAIUDice.roll(blueBases.size()) - 1).x;
    y = blueBases.get(PICAIUDice.roll(blueBases.size()) - 1).y;
    }
    }
    }
    if (imOnRedTeam) {
    addRedUnit(unit, unit.getId());
    } else {
    addBlueUnit(unit, unit.getId());
    }
    }
    toPlaceUnits.clear();
     */
  }

  public void updateUnit(String message) {
    inputQueue.add(new PICAIUUnitUpdate(message));
  }

  public boolean isRunning() {
    return running;
  }
}
